package com.timo.mail.util;

import com.timo.mail.model.MailBoxService;
import com.timo.mail.model.MailFile;
import com.timo.mail.model.MailMessage;

import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;


import javax.activation.URLDataSource;
import javax.mail.*;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * @Author: Timo
 * @Date: 2019/6/4 11:01
 * @Description: 邮件消息发送解析类，
 * 现在已存在的接收邮件的类型分别是imap/impa3/impa4/等类型
 */
public class SendMailHandler {
    private MailBoxService mailBoxService;
    private Transport transport;
    private Session session;
    private Properties properties;
    private JavaMailSenderImpl javaMailSender;
    private MimeMessage mimeMessage;
    private MailMessage sendMessage;
    /**
     * #############################################################
     * #                        对外暴露的方法
     * #############################################################
     */


    /**
     * 初始化,如果只是检测是否改账号是否可以使用的话，可以使用这个方法，但是如果是发送消息的话就不能使用这个方法
     *
     * @param mailBoxService
     */
    public SendMailHandler(MailBoxService mailBoxService) {
        this.mailBoxService = mailBoxService;
        init();
    }

    /**
     * 初始化,如果发送消息就使用这个方法
     *
     * @param mailBoxService
     */
    public SendMailHandler(MailBoxService mailBoxService, MailMessage mailMessage) {
        this.mailBoxService = mailBoxService;
        this.sendMessage = mailMessage;
        send_message_init();
    }



    /**
     * 校验是否连接成功，在发邮件之间都必须校验一下是否可以连接成功，成功之后才可以发送邮件
     * 也可以检验当前发送账号是否可以正常使用。
     *
     * @return
     */
    public boolean connect() {
        Assert.assertNotNull("transport is null , Please use the parameter constructor to create Instance", this.transport);
        int con_count = 0;
        while (!transport.isConnected() && con_count < 3) {
            con_count++;
            try {
                this.session.setDebug(true);
                transport.connect(mailBoxService.getHost(), mailBoxService.getAddress(), mailBoxService.getPassword());
            } catch (MessagingException e) {
                System.out.println("Handshake connection count " + con_count);
            }
        }
        return transport.isConnected();
    }


    public boolean send() {
        // 设置消息体
        if (!connect()) {
            System.out.println("连接失败");
            return false;
        }
        try {

            javaMailSender.send(mimeMessage);
            System.out.println("send is ok ");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("send is fail ");
        }
        return false;
    }

    /**
     * #############################################################
     * #                        私有方法
     * #############################################################
     */

    /**
     * 初始化方法
     */
    private void init() {
        this.properties = getProperties();
        this.session = getMailSession();
        this.transport = getTransport();

    }

    private void send_message_init() {
        // 发送邮件
        setJavaMailSender();
        setMimeMessage();
        setMimeMessageHelper();
        this.properties = getJavaMailSenderProperties();

        // 主要用于测试是否可以连接成功
        this.session = getMailSession();
        this.transport = getTransport();
    }

    /**
     * 名称	类型	描述
     * mail.smtp.user	串	SMTP的默认用户名。
     * mail.smtp.host	串	要连接的SMTP服务器。
     * mail.smtp.port	INT	要连接的SMTP服务器端口，如果connect（）方法未明确指定一个。默认为25。
     * mail.smtp.connectiontimeout	INT	套接字连接超时值，以毫秒为单位。默认为无限超时。
     * mail.smtp.timeout	INT	套接字I / O超时值，以毫秒为单位。默认为无限超时。
     * mail.smtp.from	串	用于SMTP MAIL命令的电子邮件地址。这将设置信封返回地址。默认为msg.getFrom（）或InternetAddress.getLocalAddress（）。
     * mail.smtp.localhost	串	SMTP HELO或EHLO命令中使用的本地主机名。默认为InetAddress.getLocalHost（）。getHostName（）。如果您的JDK和名称服务配置正确，通常不需要设置。
     * mail.smtp.localaddress	串	创建SMTP套接字时要绑定的本地地址（主机名）。默认为Socket类选择的地址。通常不需要设置。
     * mail.smtp.localport	INT	创建SMTP套接字时要绑定的本地端口号。默认为Socket类选择的端口号。
     * mail.smtp.ehlo	布尔	如果为false，请勿尝试使用EHLO命令登录。默认为true。
     * mail.smtp.auth	布尔	如果为true，则尝试使用AUTH命令对用户进行身份验证。默认为false。
     * mail.smtp.auth.mechanisms	串	如果设置，则列出要考虑的身份验证机制。仅使用服务器支持并由当前实现支持的机制。默认为“LOGIN PLAIN DIGEST-MD5 NTLM”，其中包括当前实现支持的所有身份验证机制。
     * mail.smtp.auth.login.disable	布尔	如果为true，则阻止使用AUTH LOGIN命令。默认值为false。
     * mail.smtp.auth.plain.disable	布尔	如果为true，则阻止使用AUTH PLAIN命令。默认值为false。
     * mail.smtp.auth.digest-md5.disable	布尔	如果为true，则阻止使用AUTH DIGEST-MD5命令。默认值为false。
     * mail.smtp.auth.ntlm.disable	布尔	如果为true，则阻止使用AUTH NTLM命令。默认值为false。
     * mail.smtp.auth.ntlm.domain	串	NTLM身份验证域。
     * mail.smtp.auth.ntlm.flags	INT	NTLM协议特定的标志。
     * mail.smtp.submitter	串	要在MAIL FROM命令中的AUTH标记中使用的提交者。通常由邮件中继使用来传递有关邮件的原始提交者的信息。
     * mail.smtp.dsn.notify	串	RCPT命令的NOTIFY选项。永远不会，或成功，失败和延迟的某种组合（以逗号分隔）。
     * mail.smtp.dsn.ret	串	MAIL命令的RET选项。FULL或HDRS。
     * mail.smtp.sendpartial	布尔	如果设置为true，并且消息包含一些有效和一些无效地址，则无论如何都要发送消息，并使用SendFailedException报告部分失败。如果设置为false（默认值），则如果收件人地址无效，则不会将邮件发送给任何收件人。
     * mail.smtp.sasl.enable	布尔	如果设置为true，请尝试使用javax.security.sasl包来选择登录的身份验证机制。默认为false。
     * mail.smtp.sasl.mechanisms	串	要尝试使用的空格或逗号分隔的SASL机制名称列表。
     * mail.smtp.sasl.authorizationid	串	要在SASL身份验证中使用的授权标识。如果未设置，则使用验证ID（用户名）。
     * mail.smtp.sasl.realm	串	与DIGEST-MD5身份验证一起使用的领域。
     * mail.smtp.quitwait	布尔	如果设置为false，则发送QUIT命令并立即关闭连接。如果设置为true（默认值），则导致传输等待对QUIT命令的响应。
     * mail.smtp.reportsuccess	布尔	如果设置为true，则导致传输为每个成功的地址包含SMTPAddressSucceededException。
     * mail.smtp.socketFactory	套接字工厂	如果设置为实现javax.net.SocketFactory接口的类，则此类将用于创建SMTP套接字。
     * mail.smtp.socketFactory.class	串	如果设置，则指定实现javax.net.SocketFactory接口的类的名称。此类将用于创建SMTP套接字。
     * mail.smtp.socketFactory.fallback	布尔	如果设置为true，则无法使用指定的套接字工厂类创建套接字将导致使用java.net.Socket类创建套接字。默认为true。
     * mail.smtp.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。如果未设置，将使用默认端口。
     * mail.smtp.ssl.enable	布尔	如果设置为true，则默认情况下使用SSL连接并使用SSL端口。“smtp”协议默认为false，“smtps”协议默认为true。
     * mail.smtp.ssl.checkserveridentity	布尔	如果设置为true，则检查RFC 2595指定的服务器标识。默认为false。
     * mail.smtp.ssl.trust	串	如果设置，并且尚未指定套接字工厂，则启用MailSSLSocketFactory。
     * 如果设置为“*”，则所有主机都是可信的。
     * 如果设置为以空格分隔的主机列表，则这些主机是受信任的。
     * 否则，信任取决于服务器提供的证书。
     * mail.smtp.ssl.socketFactory	SSL套接字工厂	如果设置为扩展javax.net.ssl.SSLSocketFactory类的类，则此类将用于创建SMTP SSL套接字。
     * mail.smtp.ssl.socketFactory.class	串	如果设置，则指定扩展javax.net.ssl.SSLSocketFactory类的类的名称。此类将用于创建SMTP SSL套接字。
     * mail.smtp.ssl.socketFactory.port	INT	指定使用指定套接字工厂时要连接的端口。如果未设置，将使用默认端口。
     * mail.smtp.ssl.protocols	串	指定将为SSL连接启用的SSL协议。属性值是javax.net.ssl.SSLSocket.setEnabledProtocols方法可接受的以空格分隔的标记列表。
     * mail.smtp.starttls.enable	布尔	如果为true，则允许使用STARTTLS命令（如果服务器支持）在发出任何登录命令之前将连接切换到受TLS保护的连接。默认为false。
     * mail.smtp.starttls.required	布尔	如果为true，则需要使用STARTTLS命令。如果服务器不支持STARTTLS命令，或者命令失败，则connect方法将失败。默认为false。
     * mail.smtp.socks.host	串	指定将用于连接到邮件服务器的SOCKS5代理服务器的主机名。
     * mail.smtp.socks.port	串	指定SOCKS5代理服务器的端口号。如果代理服务器未使用标准端口号1080，则只需使用此选项。
     * mail.smtp.mailextension	串	要附加到MAIL命令的扩展字符串。
     * mail.smtp.userset	布尔	如果设置为true，则在isConnected方法中使用RSET命令而不是NOOP命令。在某些情况下，sendmail会在许多NOOP命令后缓慢响应; 使用RSET避免了这个sendmail问题。默认为false。
     */
    private void setProperties() {
        Assert.assertNotNull("properties is null , Please specify first  properties type ",properties);
        /***基础配置****/
        properties.put("mail.transport.protocol", mailBoxService.getProtocol());
        properties.put("mail.default-encoding", "UTF-8");
        properties.put("mail.smtp.auth", "true");
        properties.put("mail.smtp.timeout", Integer.toString(1000_20));
        // 解决附件中名字过长的情况出现
        // 如果附件文件名过长的时候是否截断,全局设置
        properties.put("mail.mime.charset", "UTF-8");
        System.setProperty("mail.mime.splitlongparameters", "false");
        System.setProperty("mail.mime.charset", "UTF-8");
        /***SSL认证的方式****/
        if (mailBoxService.getSsl()) {
            properties.put("mail.smtp.port", mailBoxService.getPort()+"");
            properties.put("mail.smtp.socketFactory.port", mailBoxService.getPort()+"");
            properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            properties.put("mail.smtp.socketFactory.fallback", "false");
            properties.put("mail.smtp.ssl.enable", true);
        }

        /***个别邮箱配置****/
    }

    /**
     * qq 邮箱配置文件
     */
    private void setQQProperties() {

    }

    /**
     * 新浪邮箱配置文件
     */
    private void setSinaQProperties() {

    }

    /**
     * qq企业邮箱配置文件
     */
    private void setExmailProperties() {

    }


    private Properties getProperties() {
        if (this.properties == null) {
            this.properties = new Properties();
            setProperties();
        }
        return properties;
    }

    private Properties getJavaMailSenderProperties() {
        if (this.properties == null) {
            this.properties = javaMailSender.getJavaMailProperties();
            setProperties();
        }
        return properties;
    }

    private Session getMailSession() {
        return Session.getInstance(properties);
    }

    /**
     * 获取发送邮件的连接
     *
     * @return
     */
    private Transport getTransport() {
        try {
            return session.getTransport(mailBoxService.getProtocol());
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 创建邮件消息体
     */
    private MimeMessage getMimeMessage() {
        return Optional.ofNullable(this.mimeMessage).orElse(javaMailSender.createMimeMessage());
    }

    private void setMimeMessage() {
        this.mimeMessage = getMimeMessage();
    }


    /**
     * ---------------- 设置发送邮件的地址、端口、账号、密码----------------------
     */
    private void setJavaMailSender() {
        javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost(mailBoxService.getHost());
        javaMailSender.setPort(mailBoxService.getPort());
        javaMailSender.setUsername(mailBoxService.getAddress());
        javaMailSender.setPassword(mailBoxService.getPassword());
    }


    /**
     * ---------------设置发送消息的结构-------------
     * -- 发件人
     * -- 抄送人
     * -- 秘密抄送人
     * -- 主题
     * -- 内容
     * -- 附件
     */
    private void setMimeMessageHelper() {
        MimeMessageHelper helper = null;
        Assert.assertNotNull("mailMessage is null", sendMessage);
        Assert.assertNotNull("mimeMessage is null", mimeMessage);
        try {
            helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
            helper.setTo(getTo());
            helper.setFrom(sendMessage.getFrom());
            helper.setSubject(sendMessage.getSubject());
            helper.setText(sendMessage.getContent(), sendMessage.getHtml());
            helper.setCc(getCc());
            addAnnex(helper);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取发送人
     *
     * @return
     */
    private String[] getTo() {
        if (!StringUtils.isEmpty(sendMessage.getTo())) {
            return sendMessage.getTo().split(";");
        }
        return null;
    }

    /**
     * 获取抄送人
     *
     * @return
     */
    private String[] getCc() {
        if (StringUtils.isNotBlank(sendMessage.getCc())) {
            String[] ccs = (String[]) sendMessage.getCc().split("");
            return ccs;
        }
        return null;
    }


    /**
     * 添加附件，附件的上传有三种形式
     * 1 根据文件的本地绝对路径获取文件
     * 2 根据文件在服务器上的地址获取文件
     * 3 文件的子节内容
     *
     * @param helper
     */
    private void addAnnex(MimeMessageHelper helper) {

        if (sendMessage.getFileList() != null && sendMessage.getFileList().size() > 0) {
            sendMessage.getFileList().forEach(file -> {
                switch (annexType(file)) {
                    // file url
                    case 1:
                        // 根据文件本地地址添加附件
                        addAnnexByFileUrl(helper,file);
                        break;
                    // http url
                    case 2:
                        // 根据文件服务器地址添加附件
                        addAnnexByHttpUrl(helper,file);
                        break;
                    // byte
                    case 3:
                        // 根据文件内容添加附件
                        addAnnexByFileByte(helper,file);
                        break;
                }
            });
        }
    }

    /**
     * 获取文件在本地的绝对地址，上传文件
     *
     * @param mailFile
     */
    private void addAnnexByFileUrl(MimeMessageHelper helper, MailFile mailFile) {
        try {
            helper.addAttachment(mailFile.getFileName(), new File(mailFile.getFileUrl()));
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取文件在服务器上地址，上传文件
     *
     * @param mailFile
     */
    private void addAnnexByHttpUrl(MimeMessageHelper helper, MailFile mailFile) {
        URL url = null;
        try {
            url = new URL(mailFile.getHttpUrl());
            URLDataSource ds = new URLDataSource(url);
            helper.addAttachment(mailFile.getFileName(), ds);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (MessagingException e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取文件的内容，上传文件
     *
     * @param mailFile
     */
    private void addAnnexByFileByte(MimeMessageHelper helper, MailFile mailFile) {
        try {
            Resource resource = new ByteArrayResource(mailFile.getFileByte());
            helper.addAttachment(mailFile.getFileName(), resource);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据附件的内容判断附件的来源是属于哪一种来源
     * @param file  附件信息
     * @return
     * 1 file绝对地址
     * 2 file的服务器地址
     * 3 file的子节内容
     * -1 没有附件
     */
    private int annexType(MailFile file) {
        if (!StringUtils.isEmpty(file.getFileUrl())
                && StringUtils.isEmpty(file.getHttpUrl())
                && file.getFileByte() == null) {
            return 1;
        } else if (!StringUtils.isEmpty(file.getHttpUrl())
                && StringUtils.isEmpty(file.getFileUrl())
                && file.getFileByte() == null) {
            return 2;
        } else if (StringUtils.isEmpty(file.getHttpUrl())
                && StringUtils.isEmpty(file.getFileUrl())
                && (file.getFileByte() != null && file.getFileByte().length > 0)) {
            return 3;
        } else if (!StringUtils.isEmpty(file.getFileUrl())) {
            return 1;
        } else if (!StringUtils.isEmpty(file.getHttpUrl())) {
            return 2;
        } else if ((file.getFileByte() != null && file.getFileByte().length > 0)) {
            return 3;
        }
        return -1;
    }


    public static void main(String[] args) {
//        smtp_qq_test();
//        smtp_email_test();
//        smtp_sina_test();
        smtp_email_send_test();
//        send_test();
    }

    public static void smtp_qq_test() {
        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("1002512666@qq.com");
        mailBox.setPassword("XXXXXX");
        mailBox.setProtocol("smtp");
        mailBox.setHost("smtp.qq.com");
        mailBox.setPort(465);
        mailBox.setSsl(true);


        SendMailHandler sendMailHandler = new SendMailHandler(mailBox);
        boolean connect = sendMailHandler.connect();
        System.out.println("--->connect:" + connect);
    }

    public static void smtp_email_test() {
        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("timo@storlead.com");
        mailBox.setPassword("XXXXXX");
        mailBox.setProtocol("smtp");
        mailBox.setHost("smtp.exmail.qq.com");
        mailBox.setPort(465);
        mailBox.setSsl(true);


        SendMailHandler sendMailHandler = new SendMailHandler(mailBox);
        boolean connect = sendMailHandler.connect();
        System.out.println("--->connect:" + connect);
    }


    public static void smtp_sina_test() {
        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("cl935@sina.com");
        mailBox.setPassword("XXXXXX");
        mailBox.setProtocol("smtp");
        mailBox.setHost("smtp.sina.com");
        mailBox.setPort(25);
        mailBox.setSsl(true);


        SendMailHandler sendMailHandler = new SendMailHandler(mailBox);
        boolean connect = sendMailHandler.connect();
        System.out.println("--->connect:" + connect);
    }


    public static void smtp_email_send_test() {
        MailBoxService mailBox = new MailBoxService();
        mailBox.setAddress("timo@storlead.com");
        mailBox.setPassword("XXXXXX");
        mailBox.setProtocol("smtp");
        mailBox.setHost("smtp.exmail.qq.com");
        mailBox.setPort(465);
        mailBox.setSsl(true);

        MailMessage message = new MailMessage();
        String fromEmailAddress = mailBox.getAddress();
        message.setFrom(fromEmailAddress);
        message.setTo("10025126666@qq.com");
        message.setCc("15662174827@163.com");
        message.setSubject("测试邮件主题===11111111");
        message.setContent("423211111111111111111");
        message.setHtml(true);
        message.setSendDate(new Date());
        message.setFileList(Arrays.asList());

        List<MailFile> list = new ArrayList<>();
        MailFile mailFile_1 = new MailFile();
        mailFile_1.setFileName("1.txt");
        mailFile_1.setFileUrl("D:\\downFile\\1.txt");
        list.add(mailFile_1);


        MailFile mailFile_2 = new MailFile();
        mailFile_2.setFileName("2.png");
        mailFile_2.setHttpUrl("https://storelead.oss-cn-shenzhen.aliyuncs.com/20181220104352_027934427044862653_Z3%202.5%E5%AF%B8SAS_2.png");
        list.add(mailFile_2);

        message.setFileList(list);

        SendMailHandler sendMailHandler = new SendMailHandler(mailBox,message);
        sendMailHandler.send();
    }


    public static void send_test() {
        try {
            MailBoxService mailBox = new MailBoxService();
            mailBox.setAddress("cl935@sina.com");
            mailBox.setPassword("XXXXXX");
            mailBox.setProtocol("smtp");
            mailBox.setHost("smtp.sina.com");
            mailBox.setPort(465);
            mailBox.setSsl(true);


            JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
            javaMailSender.setHost(mailBox.getHost());
            javaMailSender.setPort(mailBox.getPort());
            javaMailSender.setUsername(mailBox.getAddress());
            javaMailSender.setPassword(mailBox.getPassword());

            Properties properties = javaMailSender.getJavaMailProperties();
            properties.setProperty("mail.transport.protocol", mailBox.getProtocol());
            properties.setProperty("mail.default-encoding", "UTF-8");
            properties.setProperty("mail.smtp.auth", "true");
            properties.setProperty("mail.smtp.timeout", Integer.toString(1000_20));
            properties.setProperty("mail.smtp.port", mailBox.getPort()+"");
            properties.setProperty("mail.smtp.socketFactory.port", mailBox.getPort()+"");
            properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            properties.setProperty("mail.smtp.socketFactory.fallback", "false");
            // 解决附件中名字过长的情况出现
            // 如果附件文件名过长的时候是否截断,全局设置
            properties.setProperty("mail.mime.charset", "UTF-8");
            System.setProperty("mail.mime.splitlongparameters", "false");
            System.setProperty("mail.mime.charset", "UTF-8");


            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
            helper.setFrom(mailBox.getAddress());
            helper.setTo("1002512666@qq.com");
            helper.setSubject("ssssssssssssss");
            helper.setText("bbbbbbbbbbbbbbbbbb");
            helper.setSentDate(new Date());

            javaMailSender.send(mimeMessage);
            System.out.println("发送成功");
        } catch (MessagingException e) {
            e.printStackTrace();
            System.out.println("发送失败");
        }

    }
}
